/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xerces.internal.impl.dtd;

import com.sun.org.apache.xerces.internal.impl.Constants;
import com.sun.org.apache.xerces.internal.impl.RevalidationHandler;
import com.sun.org.apache.xerces.internal.impl.XMLEntityManager;
import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
import com.sun.org.apache.xerces.internal.impl.dtd.models.ContentModelValidator;
import com.sun.org.apache.xerces.internal.impl.dv.DTDDVFactory;
import com.sun.org.apache.xerces.internal.impl.dv.DatatypeValidator;
import com.sun.org.apache.xerces.internal.impl.dv.InvalidDatatypeValueException;
import com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter;
import com.sun.org.apache.xerces.internal.impl.validation.ValidationManager;
import com.sun.org.apache.xerces.internal.impl.validation.ValidationState;
import com.sun.org.apache.xerces.internal.util.SymbolTable;
import com.sun.org.apache.xerces.internal.util.XMLChar;
import com.sun.org.apache.xerces.internal.util.XMLSymbols;
import com.sun.org.apache.xerces.internal.xni.Augmentations;
import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
import com.sun.org.apache.xerces.internal.xni.QName;
import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
import com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler;
import com.sun.org.apache.xerces.internal.xni.XMLLocator;
import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
import com.sun.org.apache.xerces.internal.xni.XMLString;
import com.sun.org.apache.xerces.internal.xni.XNIException;
import com.sun.org.apache.xerces.internal.xni.grammars.Grammar;
import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarDescription;
import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarPool;
import com.sun.org.apache.xerces.internal.xni.parser.XMLComponent;
import com.sun.org.apache.xerces.internal.xni.parser.XMLComponentManager;
import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
import com.sun.org.apache.xerces.internal.xni.parser.XMLDocumentFilter;
import com.sun.org.apache.xerces.internal.xni.parser.XMLDocumentSource;

/**
 * The DTD validator. The validator implements a document
 * filter: receiving document events from the scanner; validating
 * the content and structure; augmenting the InfoSet, if applicable;
 * and notifying the parser of the information resulting from the
 * validation process.
 * <p> Formerly, this component also handled DTD events and grammar construction.
 * To facilitate the development of a meaningful DTD grammar caching/preparsing
 * framework, this functionality has been moved into the XMLDTDLoader
 * class.  Therefore, this class no longer implements the DTDFilter
 * or DTDContentModelFilter interfaces.
 * <p>
 * This component requires the following features and properties from the
 * component manager that uses it:
 * <ul>
 * <li>http://xml.org/sax/features/namespaces</li>
 * <li>http://xml.org/sax/features/validation</li>
 * <li>http://apache.org/xml/features/validation/dynamic</li>
 * <li>http://apache.org/xml/properties/internal/symbol-table</li>
 * <li>http://apache.org/xml/properties/internal/error-reporter</li>
 * <li>http://apache.org/xml/properties/internal/grammar-pool</li>
 * <li>http://apache.org/xml/properties/internal/datatype-validator-factory</li>
 * </ul>
 *
 * @author Eric Ye, IBM
 * @author Andy Clark, IBM
 * @author Jeffrey Rodriguez IBM
 * @author Neil Graham, IBM
 * @version $Id: XMLDTDValidator.java,v 1.8 2010-11-01 04:39:42 joehw Exp $
 * @xerces.internal
 */
public class XMLDTDValidator
    implements XMLComponent, XMLDocumentFilter, XMLDTDValidatorFilter, RevalidationHandler {

  //
  // Constants
  //

  /** Symbol: "&lt;&lt;datatypes>>". */

  /**
   * Top level scope (-1).
   */
  private static final int TOP_LEVEL_SCOPE = -1;

  // feature identifiers

  /**
   * Feature identifier: namespaces.
   */
  protected static final String NAMESPACES =
      Constants.SAX_FEATURE_PREFIX + Constants.NAMESPACES_FEATURE;

  /**
   * Feature identifier: validation.
   */
  protected static final String VALIDATION =
      Constants.SAX_FEATURE_PREFIX + Constants.VALIDATION_FEATURE;

  /**
   * Feature identifier: dynamic validation.
   */
  protected static final String DYNAMIC_VALIDATION =
      Constants.XERCES_FEATURE_PREFIX + Constants.DYNAMIC_VALIDATION_FEATURE;

  /**
   * Feature identifier: balance syntax trees.
   */
  protected static final String BALANCE_SYNTAX_TREES =
      Constants.XERCES_FEATURE_PREFIX + Constants.BALANCE_SYNTAX_TREES;

  /**
   * Feature identifier: warn on duplicate attdef
   */
  protected static final String WARN_ON_DUPLICATE_ATTDEF =
      Constants.XERCES_FEATURE_PREFIX + Constants.WARN_ON_DUPLICATE_ATTDEF_FEATURE;

  protected static final String PARSER_SETTINGS =
      Constants.XERCES_FEATURE_PREFIX + Constants.PARSER_SETTINGS;

  // property identifiers

  /**
   * Property identifier: symbol table.
   */
  protected static final String SYMBOL_TABLE =
      Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;

  /**
   * Property identifier: error reporter.
   */
  protected static final String ERROR_REPORTER =
      Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;

  /**
   * Property identifier: grammar pool.
   */
  protected static final String GRAMMAR_POOL =
      Constants.XERCES_PROPERTY_PREFIX + Constants.XMLGRAMMAR_POOL_PROPERTY;

  /**
   * Property identifier: datatype validator factory.
   */
  protected static final String DATATYPE_VALIDATOR_FACTORY =
      Constants.XERCES_PROPERTY_PREFIX + Constants.DATATYPE_VALIDATOR_FACTORY_PROPERTY;

  // property identifier:  ValidationManager
  protected static final String VALIDATION_MANAGER =
      Constants.XERCES_PROPERTY_PREFIX + Constants.VALIDATION_MANAGER_PROPERTY;

  // recognized features and properties

  /**
   * Recognized features.
   */
  private static final String[] RECOGNIZED_FEATURES = {
      NAMESPACES,
      VALIDATION,
      DYNAMIC_VALIDATION,
      BALANCE_SYNTAX_TREES
  };

  /**
   * Feature defaults.
   */
  private static final Boolean[] FEATURE_DEFAULTS = {
      null,
      null,
      Boolean.FALSE,
      Boolean.FALSE,
  };

  /**
   * Recognized properties.
   */
  private static final String[] RECOGNIZED_PROPERTIES = {
      SYMBOL_TABLE,
      ERROR_REPORTER,
      GRAMMAR_POOL,
      DATATYPE_VALIDATOR_FACTORY,
      VALIDATION_MANAGER
  };

  /**
   * Property defaults.
   */
  private static final Object[] PROPERTY_DEFAULTS = {
      null,
      null,
      null,
      null,
      null,
  };

  // debugging

  /**
   * Compile to true to debug attributes.
   */
  private static final boolean DEBUG_ATTRIBUTES = false;

  /**
   * Compile to true to debug element children.
   */
  private static final boolean DEBUG_ELEMENT_CHILDREN = false;

  //
  // Data
  //

  // updated during reset
  protected ValidationManager fValidationManager = null;

  // validation state
  protected final ValidationState fValidationState = new ValidationState();

  // features

  /**
   * Namespaces.
   */
  protected boolean fNamespaces;

  /**
   * Validation.
   */
  protected boolean fValidation;

  /**
   * Validation against only DTD
   */
  protected boolean fDTDValidation;

  /**
   * Dynamic validation. This state of this feature is only useful when
   * the validation feature is set to <code>true</code>.
   */
  protected boolean fDynamicValidation;

  /**
   * Controls whether the DTD grammar produces balanced syntax trees.
   */
  protected boolean fBalanceSyntaxTrees;

  /**
   * warn on duplicate attribute definition, this feature works only when validation is true
   */
  protected boolean fWarnDuplicateAttdef;

  // properties

  /**
   * Symbol table.
   */
  protected SymbolTable fSymbolTable;

  /**
   * Error reporter.
   */
  protected XMLErrorReporter fErrorReporter;

  // the grammar pool
  protected XMLGrammarPool fGrammarPool;

  /**
   * Grammar bucket.
   */
  protected DTDGrammarBucket fGrammarBucket;

  /* location of the document as passed in from startDocument call */
  protected XMLLocator fDocLocation;

  /**
   * Namespace support.
   */
  protected NamespaceContext fNamespaceContext = null;

  /**
   * Datatype validator factory.
   */
  protected DTDDVFactory fDatatypeValidatorFactory;

  // handlers

  /**
   * Document handler.
   */
  protected XMLDocumentHandler fDocumentHandler;

  protected XMLDocumentSource fDocumentSource;
  // grammars

  /**
   * DTD Grammar.
   */
  protected DTDGrammar fDTDGrammar;

  // state

  /**
   * True if seen DOCTYPE declaration.
   */
  protected boolean fSeenDoctypeDecl = false;

  /**
   * Perform validation.
   */
  private boolean fPerformValidation;

  /**
   * Schema type: None, DTD, Schema
   */
  private String fSchemaType;

  // information regarding the current element

  /**
   * Current element name.
   */
  private final QName fCurrentElement = new QName();

  /**
   * Current element index.
   */
  private int fCurrentElementIndex = -1;

  /**
   * Current content spec type.
   */
  private int fCurrentContentSpecType = -1;

  /**
   * The root element name.
   */
  private final QName fRootElement = new QName();

  private boolean fInCDATASection = false;
  // element stack

  /**
   * Element index stack.
   */
  private int[] fElementIndexStack = new int[8];

  /**
   * Content spec type stack.
   */
  private int[] fContentSpecTypeStack = new int[8];

  /**
   * Element name stack.
   */
  private QName[] fElementQNamePartsStack = new QName[8];

  // children list and offset stack

  /**
   * Element children. This data structure is a growing stack that
   * holds the children of elements from the root to the current
   * element depth. This structure never gets "deeper" than the
   * deepest element. Space is re-used once each element is closed.
   * <p>
   * <strong>Note:</strong> This is much more efficient use of memory
   * than creating new arrays for each element depth.
   * <p>
   * <strong>Note:</strong> The use of this data structure is for
   * validation "on the way out". If the validation model changes to
   * "on the way in", then this data structure is not needed.
   */
  private QName[] fElementChildren = new QName[32];

  /**
   * Element children count.
   */
  private int fElementChildrenLength = 0;

  /**
   * Element children offset stack. This stack refers to offsets
   * into the <code>fElementChildren</code> array.
   *
   * @see #fElementChildren
   */
  private int[] fElementChildrenOffsetStack = new int[32];

  /**
   * Element depth.
   */
  private int fElementDepth = -1;

  // validation states

  /**
   * True if seen the root element.
   */
  private boolean fSeenRootElement = false;

  /**
   * True if inside of element content.
   */
  private boolean fInElementContent = false;

  // temporary variables

  /**
   * Temporary element declaration.
   */
  private XMLElementDecl fTempElementDecl = new XMLElementDecl();

  /**
   * Temporary atribute declaration.
   */
  private final XMLAttributeDecl fTempAttDecl = new XMLAttributeDecl();

  /**
   * Temporary entity declaration.
   */
  private final XMLEntityDecl fEntityDecl = new XMLEntityDecl();

  /**
   * Temporary qualified name.
   */
  private final QName fTempQName = new QName();

  /**
   * Temporary string buffers.
   */
  private final StringBuffer fBuffer = new StringBuffer();

  // symbols: general

  // attribute validators

  /**
   * Datatype validator: ID.
   */
  protected DatatypeValidator fValID;

  /**
   * Datatype validator: IDREF.
   */
  protected DatatypeValidator fValIDRef;

  /**
   * Datatype validator: IDREFS.
   */
  protected DatatypeValidator fValIDRefs;

  /**
   * Datatype validator: ENTITY.
   */
  protected DatatypeValidator fValENTITY;

  /**
   * Datatype validator: ENTITIES.
   */
  protected DatatypeValidator fValENTITIES;

  /**
   * Datatype validator: NMTOKEN.
   */
  protected DatatypeValidator fValNMTOKEN;

  /**
   * Datatype validator: NMTOKENS.
   */
  protected DatatypeValidator fValNMTOKENS;

  /**
   * Datatype validator: NOTATION.
   */
  protected DatatypeValidator fValNOTATION;

  // to check for duplicate ID or ANNOTATION attribute declare in
  // ATTLIST, and misc VCs

  //
  // Constructors
  //

  /**
   * Default constructor.
   */
  public XMLDTDValidator() {

    // initialize data
    for (int i = 0; i < fElementQNamePartsStack.length; i++) {
      fElementQNamePartsStack[i] = new QName();
    }
    fGrammarBucket = new DTDGrammarBucket();

  } // <init>()

  DTDGrammarBucket getGrammarBucket() {
    return fGrammarBucket;
  } // getGrammarBucket():  DTDGrammarBucket

  //
  // XMLComponent methods
  //

  /*
   * Resets the component. The component can query the component manager
   * about any features and properties that affect the operation of the
   * component.
   *
   * @param componentManager The component manager.
   *
   * @throws SAXException Thrown by component on finitialization error.
   *                      For example, if a feature or property is
   *                      required for the operation of the component, the
   *                      component manager may throw a
   *                      SAXNotRecognizedException or a
   *                      SAXNotSupportedException.
   */
  public void reset(XMLComponentManager componentManager)
      throws XMLConfigurationException {

    // clear grammars
    fDTDGrammar = null;
    fSeenDoctypeDecl = false;
    fInCDATASection = false;
    // initialize state
    fSeenRootElement = false;
    fInElementContent = false;
    fCurrentElementIndex = -1;
    fCurrentContentSpecType = -1;

    fRootElement.clear();

    fValidationState.resetIDTables();

    fGrammarBucket.clear();
    fElementDepth = -1;
    fElementChildrenLength = 0;

    boolean parser_settings = componentManager.getFeature(PARSER_SETTINGS, true);

    if (!parser_settings) {
      // parser settings have not been changed
      fValidationManager.addValidationState(fValidationState);
      return;
    }

    // sax features
    fNamespaces = componentManager.getFeature(NAMESPACES, true);
    fValidation = componentManager.getFeature(VALIDATION, false);
    fDTDValidation = !(componentManager
        .getFeature(Constants.XERCES_FEATURE_PREFIX + Constants.SCHEMA_VALIDATION_FEATURE, false));

    // Xerces features
    fDynamicValidation = componentManager.getFeature(DYNAMIC_VALIDATION, false);
    fBalanceSyntaxTrees = componentManager.getFeature(BALANCE_SYNTAX_TREES, false);
    fWarnDuplicateAttdef = componentManager.getFeature(WARN_ON_DUPLICATE_ATTDEF, false);

    fSchemaType = (String) componentManager.getProperty(Constants.JAXP_PROPERTY_PREFIX
        + Constants.SCHEMA_LANGUAGE, null);

    fValidationManager = (ValidationManager) componentManager.getProperty(VALIDATION_MANAGER);
    fValidationManager.addValidationState(fValidationState);
    fValidationState.setUsingNamespaces(fNamespaces);

    // get needed components
    fErrorReporter = (XMLErrorReporter) componentManager
        .getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY);
    fSymbolTable = (SymbolTable) componentManager
        .getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY);
    fGrammarPool = (XMLGrammarPool) componentManager.getProperty(GRAMMAR_POOL, null);

    fDatatypeValidatorFactory = (DTDDVFactory) componentManager.getProperty(
        Constants.XERCES_PROPERTY_PREFIX + Constants.DATATYPE_VALIDATOR_FACTORY_PROPERTY);
    init();

  } // reset(XMLComponentManager)

  /**
   * Returns a list of feature identifiers that are recognized by
   * this component. This method may return null if no features
   * are recognized by this component.
   */
  public String[] getRecognizedFeatures() {
    return (String[]) (RECOGNIZED_FEATURES.clone());
  } // getRecognizedFeatures():String[]

  /**
   * Sets the state of a feature. This method is called by the component
   * manager any time after reset when a feature changes state.
   * <p>
   * <strong>Note:</strong> Components should silently ignore features
   * that do not affect the operation of the component.
   *
   * @param featureId The feature identifier.
   * @param state The state of the feature.
   * @throws SAXNotRecognizedException The component should not throw this exception.
   * @throws SAXNotSupportedException The component should not throw this exception.
   */
  public void setFeature(String featureId, boolean state)
      throws XMLConfigurationException {
  } // setFeature(String,boolean)

  /**
   * Returns a list of property identifiers that are recognized by
   * this component. This method may return null if no properties
   * are recognized by this component.
   */
  public String[] getRecognizedProperties() {
    return (String[]) (RECOGNIZED_PROPERTIES.clone());
  } // getRecognizedProperties():String[]

  /**
   * Sets the value of a property. This method is called by the component
   * manager any time after reset when a property changes value.
   * <p>
   * <strong>Note:</strong> Components should silently ignore properties
   * that do not affect the operation of the component.
   *
   * @param propertyId The property identifier.
   * @param value The value of the property.
   * @throws SAXNotRecognizedException The component should not throw this exception.
   * @throws SAXNotSupportedException The component should not throw this exception.
   */
  public void setProperty(String propertyId, Object value)
      throws XMLConfigurationException {
  } // setProperty(String,Object)

  /**
   * Returns the default state for a feature, or null if this
   * component does not want to report a default value for this
   * feature.
   *
   * @param featureId The feature identifier.
   * @since Xerces 2.2.0
   */
  public Boolean getFeatureDefault(String featureId) {
    for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
      if (RECOGNIZED_FEATURES[i].equals(featureId)) {
        return FEATURE_DEFAULTS[i];
      }
    }
    return null;
  } // getFeatureDefault(String):Boolean

  /**
   * Returns the default state for a property, or null if this
   * component does not want to report a default value for this
   * property.
   *
   * @param propertyId The property identifier.
   * @since Xerces 2.2.0
   */
  public Object getPropertyDefault(String propertyId) {
    for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
      if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
        return PROPERTY_DEFAULTS[i];
      }
    }
    return null;
  } // getPropertyDefault(String):Object

  //
  // XMLDocumentSource methods
  //

  /**
   * Sets the document handler to receive information about the document.
   */
  public void setDocumentHandler(XMLDocumentHandler documentHandler) {
    fDocumentHandler = documentHandler;
  } // setDocumentHandler(XMLDocumentHandler)

  /**
   * Returns the document handler
   */
  public XMLDocumentHandler getDocumentHandler() {
    return fDocumentHandler;
  } // getDocumentHandler():  XMLDocumentHandler

  //
  // XMLDocumentHandler methods
  //

  /**
   * Sets the document source
   */
  public void setDocumentSource(XMLDocumentSource source) {
    fDocumentSource = source;
  } // setDocumentSource

  /**
   * Returns the document source
   */
  public XMLDocumentSource getDocumentSource() {
    return fDocumentSource;
  } // getDocumentSource

  /**
   * The start of the document.
   *
   * @param locator The system identifier of the entity if the entity is external, null otherwise.
   * @param encoding The auto-detected IANA encoding name of the entity stream. This value will be
   * null in those situations where the entity encoding is not auto-detected (e.g. internal entities
   * or a document entity that is parsed from a java.io.Reader).
   * @param namespaceContext The namespace context in effect at the start of this document. This
   * object represents the current context. Implementors of this class are responsible for copying
   * the namespace bindings from the the current context (and its parent contexts) if that
   * information is important.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startDocument(XMLLocator locator, String encoding,
      NamespaceContext namespaceContext, Augmentations augs)
      throws XNIException {

    // call handlers
    // get initial grammars
    if (fGrammarPool != null) {
      Grammar[] grammars = fGrammarPool.retrieveInitialGrammarSet(XMLGrammarDescription.XML_DTD);
      final int length = (grammars != null) ? grammars.length : 0;
      for (int i = 0; i < length; ++i) {
        fGrammarBucket.putGrammar((DTDGrammar) grammars[i]);
      }
    }
    fDocLocation = locator;
    fNamespaceContext = namespaceContext;

    if (fDocumentHandler != null) {
      fDocumentHandler.startDocument(locator, encoding, namespaceContext, augs);
    }

  } // startDocument(XMLLocator,String)

  /**
   * Notifies of the presence of an XMLDecl line in the document. If
   * present, this method will be called immediately following the
   * startDocument call.
   *
   * @param version The XML version.
   * @param encoding The IANA encoding name of the document, or null if not specified.
   * @param standalone The standalone value, or null if not specified.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void xmlDecl(String version, String encoding, String standalone, Augmentations augs)
      throws XNIException {

    // save standalone state
    fGrammarBucket.setStandalone(standalone != null && standalone.equals("yes"));

    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.xmlDecl(version, encoding, standalone, augs);
    }

  } // xmlDecl(String,String,String)

  /**
   * Notifies of the presence of the DOCTYPE line in the document.
   *
   * @param rootElement The name of the root element.
   * @param publicId The public identifier if an external DTD or null if the external DTD is
   * specified using SYSTEM.
   * @param systemId The system identifier if an external DTD, null otherwise.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void doctypeDecl(String rootElement, String publicId, String systemId,
      Augmentations augs)
      throws XNIException {

    // save root element state
    fSeenDoctypeDecl = true;
    fRootElement.setValues(null, rootElement, rootElement, null);
    // find or create grammar:
    String eid = null;
    try {
      eid = XMLEntityManager.expandSystemId(systemId, fDocLocation.getExpandedSystemId(), false);
    } catch (java.io.IOException e) {
    }
    XMLDTDDescription grammarDesc = new XMLDTDDescription(publicId, systemId,
        fDocLocation.getExpandedSystemId(), eid, rootElement);
    fDTDGrammar = fGrammarBucket.getGrammar(grammarDesc);
    if (fDTDGrammar == null) {
      // give grammar pool a chance...
      //
      // Do not bother checking the pool if no public or system identifier was provided.
      // Since so many different DTDs have roots in common, using only a root name as the
      // key may cause an unexpected grammar to be retrieved from the grammar pool. This scenario
      // would occur when an ExternalSubsetResolver has been queried and the
      // XMLInputSource returned contains an input stream but no external identifier.
      // This can never happen when the instance document specified a DOCTYPE. -- mrglavas
      if (fGrammarPool != null && (systemId != null || publicId != null)) {
        fDTDGrammar = (DTDGrammar) fGrammarPool.retrieveGrammar(grammarDesc);
      }
    }
    if (fDTDGrammar == null) {
      // we'll have to create it...
      if (!fBalanceSyntaxTrees) {
        fDTDGrammar = new DTDGrammar(fSymbolTable, grammarDesc);
      } else {
        fDTDGrammar = new BalancedDTDGrammar(fSymbolTable, grammarDesc);
      }
    } else {
      // we've found a cached one;so let's make sure not to read
      // any external subset!
      fValidationManager.setCachedDTD(true);
    }
    fGrammarBucket.setActiveGrammar(fDTDGrammar);

    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.doctypeDecl(rootElement, publicId, systemId, augs);
    }

  } // doctypeDecl(String,String,String, Augmentations)


  /**
   * The start of an element.
   *
   * @param element The name of the element.
   * @param attributes The element attributes.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startElement(QName element, XMLAttributes attributes, Augmentations augs)
      throws XNIException {

    handleStartElement(element, attributes, augs);
    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.startElement(element, attributes, augs);

    }

  } // startElement(QName,XMLAttributes)

  /**
   * An empty element.
   *
   * @param element The name of the element.
   * @param attributes The element attributes.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void emptyElement(QName element, XMLAttributes attributes, Augmentations augs)
      throws XNIException {

    boolean removed = handleStartElement(element, attributes, augs);

    if (fDocumentHandler != null) {
      fDocumentHandler.emptyElement(element, attributes, augs);
    }
    if (!removed) {
      handleEndElement(element, augs, true);
    }


  } // emptyElement(QName,XMLAttributes)

  /**
   * Character content.
   *
   * @param text The content.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void characters(XMLString text, Augmentations augs) throws XNIException {

    boolean callNextCharacters = true;

    // REVISIT: [Q] Is there a more efficient way of doing this?
    //          Perhaps if the scanner told us so we don't have to
    //          look at the characters again. -Ac
    boolean allWhiteSpace = true;
    for (int i = text.offset; i < text.offset + text.length; i++) {
      if (!isSpace(text.ch[i])) {
        allWhiteSpace = false;
        break;
      }
    }
    // call the ignoreableWhiteSpace callback
    // never call ignorableWhitespace if we are in cdata section
    if (fInElementContent && allWhiteSpace && !fInCDATASection) {
      if (fDocumentHandler != null) {
        fDocumentHandler.ignorableWhitespace(text, augs);
        callNextCharacters = false;
      }
    }

    // validate
    if (fPerformValidation) {
      if (fInElementContent) {
        if (fGrammarBucket.getStandalone() &&
            fDTDGrammar.getElementDeclIsExternal(fCurrentElementIndex)) {
          if (allWhiteSpace) {
            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                "MSG_WHITE_SPACE_IN_ELEMENT_CONTENT_WHEN_STANDALONE",
                null, XMLErrorReporter.SEVERITY_ERROR);
          }
        }
        if (!allWhiteSpace) {
          charDataInContent();
        }

        // For E15.2
        if (augs != null && augs.getItem(Constants.CHAR_REF_PROBABLE_WS) == Boolean.TRUE) {
          fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
              "MSG_CONTENT_INVALID_SPECIFIED",
              new Object[]{fCurrentElement.rawname,
                  fDTDGrammar.getContentSpecAsString(fElementDepth),
                  "character reference"},
              XMLErrorReporter.SEVERITY_ERROR);
        }
      }

      if (fCurrentContentSpecType == XMLElementDecl.TYPE_EMPTY) {
        charDataInContent();
      }
    }

    // call handlers
    if (callNextCharacters && fDocumentHandler != null) {
      fDocumentHandler.characters(text, augs);
    }

  } // characters(XMLString)


  /**
   * Ignorable whitespace. For this method to be called, the document
   * source must have some way of determining that the text containing
   * only whitespace characters should be considered ignorable. For
   * example, the validator can determine if a length of whitespace
   * characters in the document are ignorable based on the element
   * content model.
   *
   * @param text The ignorable whitespace.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void ignorableWhitespace(XMLString text, Augmentations augs) throws XNIException {

    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.ignorableWhitespace(text, augs);
    }

  } // ignorableWhitespace(XMLString)

  /**
   * The end of an element.
   *
   * @param element The name of the element.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endElement(QName element, Augmentations augs) throws XNIException {

    handleEndElement(element, augs, false);

  } // endElement(QName)

  /**
   * The start of a CDATA section.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startCDATA(Augmentations augs) throws XNIException {

    if (fPerformValidation && fInElementContent) {
      charDataInContent();
    }
    fInCDATASection = true;
    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.startCDATA(augs);
    }

  } // startCDATA()

  /**
   * The end of a CDATA section.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endCDATA(Augmentations augs) throws XNIException {

    fInCDATASection = false;
    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.endCDATA(augs);
    }

  } // endCDATA()

  /**
   * The end of the document.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endDocument(Augmentations augs) throws XNIException {

    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.endDocument(augs);
    }

  } // endDocument()

  /**
   * A comment.
   *
   * @param text The text in the comment.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by application to signal an error.
   */
  public void comment(XMLString text, Augmentations augs) throws XNIException {
    // fixes E15.1
    if (fPerformValidation && fElementDepth >= 0 && fDTDGrammar != null) {
      fDTDGrammar.getElementDecl(fCurrentElementIndex, fTempElementDecl);
      if (fTempElementDecl.type == XMLElementDecl.TYPE_EMPTY) {
        fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
            "MSG_CONTENT_INVALID_SPECIFIED",
            new Object[]{fCurrentElement.rawname,
                "EMPTY",
                "comment"},
            XMLErrorReporter.SEVERITY_ERROR);
      }
    }
    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.comment(text, augs);
    }

  } // comment(XMLString)


  /**
   * A processing instruction. Processing instructions consist of a
   * target name and, optionally, text data. The data is only meaningful
   * to the application.
   * <p>
   * Typically, a processing instruction's data will contain a series
   * of pseudo-attributes. These pseudo-attributes follow the form of
   * element attributes but are <strong>not</strong> parsed or presented
   * to the application as anything other than text. The application is
   * responsible for parsing the data.
   *
   * @param target The target.
   * @param data The data or null if none specified.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void processingInstruction(String target, XMLString data, Augmentations augs)
      throws XNIException {

    // fixes E15.1
    if (fPerformValidation && fElementDepth >= 0 && fDTDGrammar != null) {
      fDTDGrammar.getElementDecl(fCurrentElementIndex, fTempElementDecl);
      if (fTempElementDecl.type == XMLElementDecl.TYPE_EMPTY) {
        fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
            "MSG_CONTENT_INVALID_SPECIFIED",
            new Object[]{fCurrentElement.rawname,
                "EMPTY",
                "processing instruction"},
            XMLErrorReporter.SEVERITY_ERROR);
      }
    }
    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.processingInstruction(target, data, augs);
    }
  } // processingInstruction(String,XMLString)

  /**
   * This method notifies the start of a general entity.
   * <p>
   * <strong>Note:</strong> This method is not called for entity references
   * appearing as part of attribute values.
   *
   * @param name The name of the general entity.
   * @param identifier The resource identifier.
   * @param encoding The auto-detected IANA encoding name of the entity stream. This value will be
   * null in those situations where the entity encoding is not auto-detected (e.g. internal entities
   * or a document entity that is parsed from a java.io.Reader).
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startGeneralEntity(String name,
      XMLResourceIdentifier identifier,
      String encoding,
      Augmentations augs) throws XNIException {
    if (fPerformValidation && fElementDepth >= 0 && fDTDGrammar != null) {
      fDTDGrammar.getElementDecl(fCurrentElementIndex, fTempElementDecl);
      // fixes E15.1
      if (fTempElementDecl.type == XMLElementDecl.TYPE_EMPTY) {
        fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
            "MSG_CONTENT_INVALID_SPECIFIED",
            new Object[]{fCurrentElement.rawname,
                "EMPTY", "ENTITY"},
            XMLErrorReporter.SEVERITY_ERROR);
      }
      if (fGrammarBucket.getStandalone()) {
        XMLDTDLoader.checkStandaloneEntityRef(name, fDTDGrammar, fEntityDecl, fErrorReporter);
      }
    }
    if (fDocumentHandler != null) {
      fDocumentHandler.startGeneralEntity(name, identifier, encoding, augs);
    }
  }

  /**
   * This method notifies the end of a general entity.
   * <p>
   * <strong>Note:</strong> This method is not called for entity references
   * appearing as part of attribute values.
   *
   * @param name The name of the entity.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endGeneralEntity(String name, Augmentations augs) throws XNIException {
    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.endGeneralEntity(name, augs);
    }
  } // endEntity(String)

  /**
   * Notifies of the presence of a TextDecl line in an entity. If present,
   * this method will be called immediately following the startParameterEntity call.
   * <p>
   * <strong>Note:</strong> This method is only called for external
   * parameter entities referenced in the DTD.
   *
   * @param version The XML version, or null if not specified.
   * @param encoding The IANA encoding name of the entity.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void textDecl(String version, String encoding, Augmentations augs) throws XNIException {

    // call handlers
    if (fDocumentHandler != null) {
      fDocumentHandler.textDecl(version, encoding, augs);
    }
  }


  public final boolean hasGrammar() {

    return (fDTDGrammar != null);
  }

  public final boolean validate() {
    // Do validation if all of the following are true:
    // 1. The JAXP Schema Language property is not XML Schema
    //    REVISIT: since only DTD and Schema are supported at this time,
    //             such checking is sufficient. but if more schema types
    //             are introduced in the future, we'll need to change it
    //             to something like
    //             (fSchemaType == null || fSchemaType == NS_XML_DTD)
    // 2. One of the following is true (validation features)
    // 2.1 Dynamic validation is off, and validation is on
    // 2.2 Dynamic validation is on, and DOCTYPE was seen
    // 3 Xerces schema validation feature is off, or DOCTYPE was seen.
    return (fSchemaType != Constants.NS_XMLSCHEMA) &&
        (!fDynamicValidation && fValidation ||
            fDynamicValidation && fSeenDoctypeDecl) &&
        (fDTDValidation || fSeenDoctypeDecl);
  }

  //REVISIT:we can convert into functions.. adding default attribute values.. and one validating.

  /**
   * Add default attributes and validate.
   */
  protected void addDTDDefaultAttrsAndValidate(QName elementName, int elementIndex,
      XMLAttributes attributes)
      throws XNIException {

    // is there anything to do?
    if (elementIndex == -1 || fDTDGrammar == null) {
      return;
    }

    //
    // Check after all specified attrs are scanned
    // (1) report error for REQUIRED attrs that are missing (V_TAGc)
    // (2) add default attrs (FIXED and NOT_FIXED)
    //
    int attlistIndex = fDTDGrammar.getFirstAttributeDeclIndex(elementIndex);

    while (attlistIndex != -1) {

      fDTDGrammar.getAttributeDecl(attlistIndex, fTempAttDecl);

      if (DEBUG_ATTRIBUTES) {
        if (fTempAttDecl != null) {
          XMLElementDecl elementDecl = new XMLElementDecl();
          fDTDGrammar.getElementDecl(elementIndex, elementDecl);
          System.out.println("element: " + (elementDecl.name.localpart));
          System.out.println("attlistIndex " + attlistIndex + "\n" +
              "attName : '" + (fTempAttDecl.name.localpart) + "'\n"
              + "attType : " + fTempAttDecl.simpleType.type + "\n"
              + "attDefaultType : " + fTempAttDecl.simpleType.defaultType + "\n"
              + "attDefaultValue : '" + fTempAttDecl.simpleType.defaultValue + "'\n"
              + attributes.getLength() + "\n"
          );
        }
      }
      String attPrefix = fTempAttDecl.name.prefix;
      String attLocalpart = fTempAttDecl.name.localpart;
      String attRawName = fTempAttDecl.name.rawname;
      String attType = getAttributeTypeName(fTempAttDecl);
      int attDefaultType = fTempAttDecl.simpleType.defaultType;
      String attValue = null;

      if (fTempAttDecl.simpleType.defaultValue != null) {
        attValue = fTempAttDecl.simpleType.defaultValue;
      }

      boolean specified = false;
      boolean required = attDefaultType == XMLSimpleType.DEFAULT_TYPE_REQUIRED;
      boolean cdata = attType == XMLSymbols.fCDATASymbol;

      if (!cdata || required || attValue != null) {
        int attrCount = attributes.getLength();
        for (int i = 0; i < attrCount; i++) {
          if (attributes.getQName(i) == attRawName) {
            specified = true;
            break;
          }
        }
      }

      if (!specified) {
        if (required) {
          if (fPerformValidation) {
            Object[] args = {elementName.localpart, attRawName};
            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                "MSG_REQUIRED_ATTRIBUTE_NOT_SPECIFIED", args,
                XMLErrorReporter.SEVERITY_ERROR);
          }
        } else if (attValue != null) {
          if (fPerformValidation && fGrammarBucket.getStandalone()) {
            if (fDTDGrammar.getAttributeDeclIsExternal(attlistIndex)) {

              Object[] args = {elementName.localpart, attRawName};
              fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                  "MSG_DEFAULTED_ATTRIBUTE_NOT_SPECIFIED", args,
                  XMLErrorReporter.SEVERITY_ERROR);
            }
          }

          // add namespace information
          if (fNamespaces) {
            int index = attRawName.indexOf(':');
            if (index != -1) {
              attPrefix = attRawName.substring(0, index);
              attPrefix = fSymbolTable.addSymbol(attPrefix);
              attLocalpart = attRawName.substring(index + 1);
              attLocalpart = fSymbolTable.addSymbol(attLocalpart);
            }
          }

          // add attribute
          fTempQName.setValues(attPrefix, attLocalpart, attRawName, fTempAttDecl.name.uri);
          int newAttr = attributes.addAttribute(fTempQName, attType, attValue);
        }
      }
      // get next att decl in the Grammar for this element
      attlistIndex = fDTDGrammar.getNextAttributeDeclIndex(attlistIndex);
    }

    // now iterate through the expanded attributes for
    // 1. if every attribute seen is declared in the DTD
    // 2. check if the VC: default_fixed holds
    // 3. validate every attribute.
    int attrCount = attributes.getLength();
    for (int i = 0; i < attrCount; i++) {
      String attrRawName = attributes.getQName(i);
      boolean declared = false;
      if (fPerformValidation) {
        if (fGrammarBucket.getStandalone()) {
          // check VC: Standalone Document Declaration, entities
          // references appear in the document.
          // REVISIT: this can be combined to a single check in
          // startEntity if we add one more argument in
          // startEnity, inAttrValue
          String nonNormalizedValue = attributes.getNonNormalizedValue(i);
          if (nonNormalizedValue != null) {
            String entityName = getExternalEntityRefInAttrValue(nonNormalizedValue);
            if (entityName != null) {
              fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                  "MSG_REFERENCE_TO_EXTERNALLY_DECLARED_ENTITY_WHEN_STANDALONE",
                  new Object[]{entityName},
                  XMLErrorReporter.SEVERITY_ERROR);
            }
          }
        }
      }
      int attDefIndex = -1;
      int position =
          fDTDGrammar.getFirstAttributeDeclIndex(elementIndex);
      while (position != -1) {
        fDTDGrammar.getAttributeDecl(position, fTempAttDecl);
        if (fTempAttDecl.name.rawname == attrRawName) {
          // found the match att decl,
          attDefIndex = position;
          declared = true;
          break;
        }
        position = fDTDGrammar.getNextAttributeDeclIndex(position);
      }
      if (!declared) {
        if (fPerformValidation) {
          // REVISIT - cache the elem/attr tuple so that we only
          // give this error once for each unique occurrence
          Object[] args = {elementName.rawname, attrRawName};

          fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
              "MSG_ATTRIBUTE_NOT_DECLARED",
              args, XMLErrorReporter.SEVERITY_ERROR);
        }
        continue;
      }
      // attribute is declared

      // fTempAttDecl should have the right value set now, so
      // the following is not needed
      // fGrammar.getAttributeDecl(attDefIndex,fTempAttDecl);

      String type = getAttributeTypeName(fTempAttDecl);
      attributes.setType(i, type);
      attributes.getAugmentations(i).putItem(Constants.ATTRIBUTE_DECLARED, Boolean.TRUE);

      boolean changedByNormalization = false;
      String oldValue = attributes.getValue(i);
      String attrValue = oldValue;
      if (attributes.isSpecified(i) && type != XMLSymbols.fCDATASymbol) {
        changedByNormalization = normalizeAttrValue(attributes, i);
        attrValue = attributes.getValue(i);
        if (fPerformValidation && fGrammarBucket.getStandalone()
            && changedByNormalization
            && fDTDGrammar.getAttributeDeclIsExternal(position)
            ) {
          // check VC: Standalone Document Declaration
          fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
              "MSG_ATTVALUE_CHANGED_DURING_NORMALIZATION_WHEN_STANDALONE",
              new Object[]{attrRawName, oldValue, attrValue},
              XMLErrorReporter.SEVERITY_ERROR);
        }
      }
      if (!fPerformValidation) {
        continue;
      }
      if (fTempAttDecl.simpleType.defaultType ==
          XMLSimpleType.DEFAULT_TYPE_FIXED) {
        String defaultValue = fTempAttDecl.simpleType.defaultValue;

        if (!attrValue.equals(defaultValue)) {
          Object[] args = {elementName.localpart,
              attrRawName,
              attrValue,
              defaultValue};
          fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
              "MSG_FIXED_ATTVALUE_INVALID",
              args, XMLErrorReporter.SEVERITY_ERROR);
        }
      }

      if (fTempAttDecl.simpleType.type == XMLSimpleType.TYPE_ENTITY ||
          fTempAttDecl.simpleType.type == XMLSimpleType.TYPE_ENUMERATION ||
          fTempAttDecl.simpleType.type == XMLSimpleType.TYPE_ID ||
          fTempAttDecl.simpleType.type == XMLSimpleType.TYPE_IDREF ||
          fTempAttDecl.simpleType.type == XMLSimpleType.TYPE_NMTOKEN ||
          fTempAttDecl.simpleType.type == XMLSimpleType.TYPE_NOTATION
          ) {
        validateDTDattribute(elementName, attrValue, fTempAttDecl);
      }
    } // for all attributes

  } // addDTDDefaultAttrsAndValidate(int,XMLAttrList)

  /**
   * Checks entities in attribute values for standalone VC.
   */
  protected String getExternalEntityRefInAttrValue(String nonNormalizedValue) {
    int valLength = nonNormalizedValue.length();
    int ampIndex = nonNormalizedValue.indexOf('&');
    while (ampIndex != -1) {
      if (ampIndex + 1 < valLength &&
          nonNormalizedValue.charAt(ampIndex + 1) != '#') {
        int semicolonIndex = nonNormalizedValue.indexOf(';', ampIndex + 1);
        String entityName = nonNormalizedValue.substring(ampIndex + 1, semicolonIndex);
        entityName = fSymbolTable.addSymbol(entityName);
        int entIndex = fDTDGrammar.getEntityDeclIndex(entityName);
        if (entIndex > -1) {
          fDTDGrammar.getEntityDecl(entIndex, fEntityDecl);
          if (fEntityDecl.inExternal ||
              (entityName = getExternalEntityRefInAttrValue(fEntityDecl.value)) != null) {
            return entityName;
          }
        }
      }
      ampIndex = nonNormalizedValue.indexOf('&', ampIndex + 1);
    }
    return null;
  } // isExternalEntityRefInAttrValue(String):String

  /**
   * Validate attributes in DTD fashion.
   */
  protected void validateDTDattribute(QName element, String attValue,
      XMLAttributeDecl attributeDecl)
      throws XNIException {

    switch (attributeDecl.simpleType.type) {
      case XMLSimpleType.TYPE_ENTITY: {
        // NOTE: Save this information because invalidStandaloneAttDef
        boolean isAlistAttribute = attributeDecl.simpleType.list;

        try {
          if (isAlistAttribute) {
            fValENTITIES.validate(attValue, fValidationState);
          } else {
            fValENTITY.validate(attValue, fValidationState);
          }
        } catch (InvalidDatatypeValueException ex) {
          fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
              ex.getKey(),
              ex.getArgs(),
              XMLErrorReporter.SEVERITY_ERROR);

        }
        break;
      }

      case XMLSimpleType.TYPE_NOTATION:
      case XMLSimpleType.TYPE_ENUMERATION: {
        boolean found = false;
        String[] enumVals = attributeDecl.simpleType.enumeration;
        if (enumVals == null) {
          found = false;
        } else {
          for (int i = 0; i < enumVals.length; i++) {
            if (attValue == enumVals[i] || attValue.equals(enumVals[i])) {
              found = true;
              break;
            }
          }
        }

        if (!found) {
          StringBuffer enumValueString = new StringBuffer();
          if (enumVals != null) {
            for (int i = 0; i < enumVals.length; i++) {
              enumValueString.append(enumVals[i] + " ");
            }
          }
          fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
              "MSG_ATTRIBUTE_VALUE_NOT_IN_LIST",
              new Object[]{attributeDecl.name.rawname, attValue, enumValueString},
              XMLErrorReporter.SEVERITY_ERROR);
        }
        break;
      }

      case XMLSimpleType.TYPE_ID: {
        try {
          fValID.validate(attValue, fValidationState);
        } catch (InvalidDatatypeValueException ex) {
          fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
              ex.getKey(),
              ex.getArgs(),
              XMLErrorReporter.SEVERITY_ERROR);
        }
        break;
      }

      case XMLSimpleType.TYPE_IDREF: {
        boolean isAlistAttribute = attributeDecl.simpleType.list;//Caveat - Save this information because invalidStandaloneAttDef

        try {
          if (isAlistAttribute) {
            fValIDRefs.validate(attValue, fValidationState);
          } else {
            fValIDRef.validate(attValue, fValidationState);
          }
        } catch (InvalidDatatypeValueException ex) {
          if (isAlistAttribute) {
            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                "IDREFSInvalid",
                new Object[]{attValue},
                XMLErrorReporter.SEVERITY_ERROR);
          } else {
            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                ex.getKey(),
                ex.getArgs(),
                XMLErrorReporter.SEVERITY_ERROR);
          }

        }
        break;
      }

      case XMLSimpleType.TYPE_NMTOKEN: {
        boolean isAlistAttribute = attributeDecl.simpleType.list;//Caveat - Save this information because invalidStandaloneAttDef
        //changes fTempAttDef
        try {
          if (isAlistAttribute) {
            fValNMTOKENS.validate(attValue, fValidationState);
          } else {
            fValNMTOKEN.validate(attValue, fValidationState);
          }
        } catch (InvalidDatatypeValueException ex) {
          if (isAlistAttribute) {
            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                "NMTOKENSInvalid",
                new Object[]{attValue},
                XMLErrorReporter.SEVERITY_ERROR);
          } else {
            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                "NMTOKENInvalid",
                new Object[]{attValue},
                XMLErrorReporter.SEVERITY_ERROR);
          }
        }
        break;
      }

    } // switch

  } // validateDTDattribute(QName,String,XMLAttributeDecl)


  /**
   * Returns true if invalid standalone attribute definition.
   */
  protected boolean invalidStandaloneAttDef(QName element, QName attribute) {
    // REVISIT: This obviously needs to be fixed! -Ac
    boolean state = true;
        /*
       if (fStandaloneReader == -1) {
          return false;
       }
       // we are normalizing a default att value...  this ok?
       if (element.rawname == -1) {
          return false;
       }
       return getAttDefIsExternal(element, attribute);
       */
    return state;
  }

  //
  // Private methods
  //


  /**
   * Normalize the attribute value of a non CDATA attributes collapsing
   * sequences of space characters (x20)
   *
   * @param attributes The list of attributes
   * @param index The index of the attribute to normalize
   */
  private boolean normalizeAttrValue(XMLAttributes attributes, int index) {
    // vars
    boolean leadingSpace = true;
    boolean spaceStart = false;
    boolean readingNonSpace = false;
    int count = 0;
    int eaten = 0;
    String attrValue = attributes.getValue(index);
    char[] attValue = new char[attrValue.length()];

    fBuffer.setLength(0);
    attrValue.getChars(0, attrValue.length(), attValue, 0);
    for (int i = 0; i < attValue.length; i++) {

      if (attValue[i] == ' ') {

        // now the tricky part
        if (readingNonSpace) {
          spaceStart = true;
          readingNonSpace = false;
        }

        if (spaceStart && !leadingSpace) {
          spaceStart = false;
          fBuffer.append(attValue[i]);
          count++;
        } else {
          if (leadingSpace || !spaceStart) {
            eaten++;
            /*** BUG #3512 ***
             int entityCount = attributes.getEntityCount(index);
             for (int j = 0;  j < entityCount; j++) {
             int offset = attributes.getEntityOffset(index, j);
             int length = attributes.getEntityLength(index, j);
             if (offset <= i-eaten+1) {
             if (offset+length >= i-eaten+1) {
             if (length > 0)
             length--;
             }
             }
             else {
             if (offset > 0)
             offset--;
             }
             attributes.setEntityOffset(index, j, offset);
             attributes.setEntityLength(index, j, length);
             }
             /***/
          }
        }

      } else {
        readingNonSpace = true;
        spaceStart = false;
        leadingSpace = false;
        fBuffer.append(attValue[i]);
        count++;
      }
    }

    // check if the last appended character is a space.
    if (count > 0 && fBuffer.charAt(count - 1) == ' ') {
      fBuffer.setLength(count - 1);
      /*** BUG #3512 ***
       int entityCount = attributes.getEntityCount(index);
       for (int j=0;  j < entityCount; j++) {
       int offset = attributes.getEntityOffset(index, j);
       int length = attributes.getEntityLength(index, j);
       if (offset < count-1) {
       if (offset+length == count) {
       length--;
       }
       }
       else {
       offset--;
       }
       attributes.setEntityOffset(index, j, offset);
       attributes.setEntityLength(index, j, length);
       }
       /***/
    }
    String newValue = fBuffer.toString();
    attributes.setValue(index, newValue);
    return !attrValue.equals(newValue);
  }

  /**
   * Root element specified.
   */
  private final void rootElementSpecified(QName rootElement) throws XNIException {
    if (fPerformValidation) {
      String root1 = fRootElement.rawname;
      String root2 = rootElement.rawname;
      if (root1 == null || !root1.equals(root2)) {
        fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
            "RootElementTypeMustMatchDoctypedecl",
            new Object[]{root1, root2},
            XMLErrorReporter.SEVERITY_ERROR);
      }
    }
  } // rootElementSpecified(QName)

  /**
   * Check that the content of an element is valid.
   * <p>
   * This is the method of primary concern to the validator. This method is called
   * upon the scanner reaching the end tag of an element. At that time, the
   * element's children must be structurally validated, so it calls this method.
   * The index of the element being checked (in the decl pool), is provided as
   * well as an array of element name indexes of the children. The validator must
   * confirm that this element can have these children in this order.
   * <p>
   * This can also be called to do 'what if' testing of content models just to see
   * if they would be valid.
   * <p>
   * Note that the element index is an index into the element decl pool, whereas
   * the children indexes are name indexes, i.e. into the string pool.
   * <p>
   * A value of -1 in the children array indicates a PCDATA node. All other
   * indexes will be positive and represent child elements. The count can be
   * zero, since some elements have the EMPTY content model and that must be
   * confirmed.
   *
   * @param elementIndex The index within the <code>ElementDeclPool</code> of this element.
   * @param childCount The number of entries in the <code>children</code> array.
   * @param children The children of this element.
   * @return The value -1 if fully valid, else the 0 based index of the child that first failed. If
   * the value returned is equal to the number of children, then additional content is required to
   * reach a valid ending state.
   * @throws Exception Thrown on error.
   */
  private int checkContent(int elementIndex,
      QName[] children,
      int childOffset,
      int childCount) throws XNIException {

    fDTDGrammar.getElementDecl(elementIndex, fTempElementDecl);

    // Get the element name index from the element
    final String elementType = fCurrentElement.rawname;

    // Get out the content spec for this element
    final int contentType = fCurrentContentSpecType;

    //
    //  Deal with the possible types of content. We try to optimized here
    //  by dealing specially with content models that don't require the
    //  full DFA treatment.
    //
    if (contentType == XMLElementDecl.TYPE_EMPTY) {
      //
      //  If the child count is greater than zero, then this is
      //  an error right off the bat at index 0.
      //
      if (childCount != 0) {
        return 0;
      }
    } else if (contentType == XMLElementDecl.TYPE_ANY) {
      //
      //  This one is open game so we don't pass any judgement on it
      //  at all. Its assumed to fine since it can hold anything.
      //
    } else if (contentType == XMLElementDecl.TYPE_MIXED ||
        contentType == XMLElementDecl.TYPE_CHILDREN) {
      // Get the content model for this element, faulting it in if needed
      ContentModelValidator cmElem = null;
      cmElem = fTempElementDecl.contentModelValidator;
      int result = cmElem.validate(children, childOffset, childCount);
      return result;
    } else if (contentType == -1) {
      //REVISIT
      /****
       reportRecoverableXMLError(XMLMessages.MSG_ELEMENT_NOT_DECLARED,
       XMLMessages.VC_ELEMENT_VALID,
       elementType);
       /****/
    } else if (contentType == XMLElementDecl.TYPE_SIMPLE) {

      //REVISIT
      // this should never be reached in the case of DTD validation.

    } else {
      //REVISIT
      /****
       fErrorReporter.reportError(fErrorReporter.getLocator(),
       ImplementationMessages.XERCES_IMPLEMENTATION_DOMAIN,
       ImplementationMessages.VAL_CST,
       0,
       null,
       XMLErrorReporter.ERRORTYPE_FATAL_ERROR);
       /****/
    }

    // We succeeded
    return -1;

  } // checkContent(int,int,QName[]):int

  /**
   * Returns the content spec type for an element index.
   */
  private int getContentSpecType(int elementIndex) {

    int contentSpecType = -1;
    if (elementIndex > -1) {
      if (fDTDGrammar.getElementDecl(elementIndex, fTempElementDecl)) {
        contentSpecType = fTempElementDecl.type;
      }
    }
    return contentSpecType;
  }

  /**
   * Character data in content.
   */
  private void charDataInContent() {

    if (DEBUG_ELEMENT_CHILDREN) {
      System.out.println("charDataInContent()");
    }
    if (fElementChildren.length <= fElementChildrenLength) {
      QName[] newarray = new QName[fElementChildren.length * 2];
      System.arraycopy(fElementChildren, 0, newarray, 0, fElementChildren.length);
      fElementChildren = newarray;
    }
    QName qname = fElementChildren[fElementChildrenLength];
    if (qname == null) {
      for (int i = fElementChildrenLength; i < fElementChildren.length; i++) {
        fElementChildren[i] = new QName();
      }
      qname = fElementChildren[fElementChildrenLength];
    }
    qname.clear();
    fElementChildrenLength++;

  } // charDataInCount()

  /**
   * convert attribute type from ints to strings
   */
  private String getAttributeTypeName(XMLAttributeDecl attrDecl) {

    switch (attrDecl.simpleType.type) {
      case XMLSimpleType.TYPE_ENTITY: {
        return attrDecl.simpleType.list ? XMLSymbols.fENTITIESSymbol : XMLSymbols.fENTITYSymbol;
      }
      case XMLSimpleType.TYPE_ENUMERATION: {
        StringBuffer buffer = new StringBuffer();
        buffer.append('(');
        for (int i = 0; i < attrDecl.simpleType.enumeration.length; i++) {
          if (i > 0) {
            buffer.append('|');
          }
          buffer.append(attrDecl.simpleType.enumeration[i]);
        }
        buffer.append(')');
        return fSymbolTable.addSymbol(buffer.toString());
      }
      case XMLSimpleType.TYPE_ID: {
        return XMLSymbols.fIDSymbol;
      }
      case XMLSimpleType.TYPE_IDREF: {
        return attrDecl.simpleType.list ? XMLSymbols.fIDREFSSymbol : XMLSymbols.fIDREFSymbol;
      }
      case XMLSimpleType.TYPE_NMTOKEN: {
        return attrDecl.simpleType.list ? XMLSymbols.fNMTOKENSSymbol : XMLSymbols.fNMTOKENSymbol;
      }
      case XMLSimpleType.TYPE_NOTATION: {
        return XMLSymbols.fNOTATIONSymbol;
      }
    }
    return XMLSymbols.fCDATASymbol;

  } // getAttributeTypeName(XMLAttributeDecl):String

  /**
   * initialization
   */
  protected void init() {

    // datatype validators
    if (fValidation || fDynamicValidation) {
      try {
        //REVISIT: datatypeRegistry + initialization of datatype
        //         why do we cast to ListDatatypeValidator?
        fValID = fDatatypeValidatorFactory.getBuiltInDV(XMLSymbols.fIDSymbol);
        fValIDRef = fDatatypeValidatorFactory.getBuiltInDV(XMLSymbols.fIDREFSymbol);
        fValIDRefs = fDatatypeValidatorFactory.getBuiltInDV(XMLSymbols.fIDREFSSymbol);
        fValENTITY = fDatatypeValidatorFactory.getBuiltInDV(XMLSymbols.fENTITYSymbol);
        fValENTITIES = fDatatypeValidatorFactory.getBuiltInDV(XMLSymbols.fENTITIESSymbol);
        fValNMTOKEN = fDatatypeValidatorFactory.getBuiltInDV(XMLSymbols.fNMTOKENSymbol);
        fValNMTOKENS = fDatatypeValidatorFactory.getBuiltInDV(XMLSymbols.fNMTOKENSSymbol);
        fValNOTATION = fDatatypeValidatorFactory.getBuiltInDV(XMLSymbols.fNOTATIONSymbol);

      } catch (Exception e) {
        // should never happen
        e.printStackTrace(System.err);
      }

    }

  } // init()

  /**
   * ensure element stack capacity
   */
  private void ensureStackCapacity(int newElementDepth) {
    if (newElementDepth == fElementQNamePartsStack.length) {

      QName[] newStackOfQueue = new QName[newElementDepth * 2];
      System.arraycopy(this.fElementQNamePartsStack, 0, newStackOfQueue, 0, newElementDepth);
      fElementQNamePartsStack = newStackOfQueue;

      QName qname = fElementQNamePartsStack[newElementDepth];
      if (qname == null) {
        for (int i = newElementDepth; i < fElementQNamePartsStack.length; i++) {
          fElementQNamePartsStack[i] = new QName();
        }
      }

      int[] newStack = new int[newElementDepth * 2];
      System.arraycopy(fElementIndexStack, 0, newStack, 0, newElementDepth);
      fElementIndexStack = newStack;

      newStack = new int[newElementDepth * 2];
      System.arraycopy(fContentSpecTypeStack, 0, newStack, 0, newElementDepth);
      fContentSpecTypeStack = newStack;

    }
  } // ensureStackCapacity

  //
  // Protected methods
  //

  /**
   * Handle element
   *
   * @return true if validator is removed from the pipeline
   */
  protected boolean handleStartElement(QName element, XMLAttributes attributes, Augmentations augs)
      throws XNIException {

    // VC: Root Element Type
    // see if the root element's name matches the one in DoctypeDecl
    if (!fSeenRootElement) {
      // REVISIT: Here are current assumptions about validation features
      //          given that XMLSchema validator is in the pipeline
      //
      // http://xml.org/sax/features/validation = true
      // http://apache.org/xml/features/validation/schema = true
      //
      // [1] XML instance document only has reference to a DTD
      //  Outcome: report validation errors only against dtd.
      //
      // [2] XML instance document has only XML Schema grammars:
      //  Outcome: report validation errors only against schemas (no errors produced from DTD validator)
      //
      // [3] XML instance document has DTD and XML schemas:
      // [a] if schema language is not set outcome - validation errors reported against both grammars: DTD and schemas.
      // [b] if schema language is set to XML Schema - do not report validation errors
      //
      // if dynamic validation is on
      //            validate only against grammar we've found (depending on settings
      //            for schema feature)
      //
      //
      fPerformValidation = validate();
      fSeenRootElement = true;
      fValidationManager.setEntityState(fDTDGrammar);
      fValidationManager.setGrammarFound(fSeenDoctypeDecl);
      rootElementSpecified(element);
    }
    if (fDTDGrammar == null) {

      if (!fPerformValidation) {
        fCurrentElementIndex = -1;
        fCurrentContentSpecType = -1;
        fInElementContent = false;
      }
      if (fPerformValidation) {
        fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
            "MSG_GRAMMAR_NOT_FOUND",
            new Object[]{element.rawname},
            XMLErrorReporter.SEVERITY_ERROR);
      }
      // modify pipeline
      if (fDocumentSource != null) {
        fDocumentSource.setDocumentHandler(fDocumentHandler);
        if (fDocumentHandler != null) {
          fDocumentHandler.setDocumentSource(fDocumentSource);
        }
        return true;
      }
    } else {
      //  resolve the element
      fCurrentElementIndex = fDTDGrammar.getElementDeclIndex(element);
      //changed here.. new function for getContentSpecType
      fCurrentContentSpecType = fDTDGrammar.getContentSpecType(fCurrentElementIndex);
      if (fCurrentContentSpecType == -1 && fPerformValidation) {
        fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
            "MSG_ELEMENT_NOT_DECLARED",
            new Object[]{element.rawname},
            XMLErrorReporter.SEVERITY_ERROR);
      }

      //  0. insert default attributes
      //  1. normalize the attributes
      //  2. validate the attrivute list.
      // TO DO:
      //changed here.. also pass element name,
      addDTDDefaultAttrsAndValidate(element, fCurrentElementIndex, attributes);
    }

    // set element content state
    fInElementContent = fCurrentContentSpecType == XMLElementDecl.TYPE_CHILDREN;

    // increment the element depth, add this element's
    // QName to its enclosing element 's children list
    fElementDepth++;
    if (fPerformValidation) {
      // push current length onto stack
      if (fElementChildrenOffsetStack.length <= fElementDepth) {
        int newarray[] = new int[fElementChildrenOffsetStack.length * 2];
        System.arraycopy(fElementChildrenOffsetStack, 0, newarray, 0,
            fElementChildrenOffsetStack.length);
        fElementChildrenOffsetStack = newarray;
      }
      fElementChildrenOffsetStack[fElementDepth] = fElementChildrenLength;

      // add this element to children
      if (fElementChildren.length <= fElementChildrenLength) {
        QName[] newarray = new QName[fElementChildrenLength * 2];
        System.arraycopy(fElementChildren, 0, newarray, 0, fElementChildren.length);
        fElementChildren = newarray;
      }
      QName qname = fElementChildren[fElementChildrenLength];
      if (qname == null) {
        for (int i = fElementChildrenLength; i < fElementChildren.length; i++) {
          fElementChildren[i] = new QName();
        }
        qname = fElementChildren[fElementChildrenLength];
      }
      qname.setValues(element);
      fElementChildrenLength++;
    }

    // save current element information
    fCurrentElement.setValues(element);
    ensureStackCapacity(fElementDepth);
    fElementQNamePartsStack[fElementDepth].setValues(fCurrentElement);
    fElementIndexStack[fElementDepth] = fCurrentElementIndex;
    fContentSpecTypeStack[fElementDepth] = fCurrentContentSpecType;
    startNamespaceScope(element, attributes, augs);
    return false;

  } // handleStartElement(QName,XMLAttributes)

  protected void startNamespaceScope(QName element, XMLAttributes attributes, Augmentations augs) {
  }

  /**
   * Handle end element.
   */
  protected void handleEndElement(QName element, Augmentations augs, boolean isEmpty)
      throws XNIException {

    // decrease element depth
    fElementDepth--;

    // validate
    if (fPerformValidation) {
      int elementIndex = fCurrentElementIndex;
      if (elementIndex != -1 && fCurrentContentSpecType != -1) {
        QName children[] = fElementChildren;
        int childrenOffset = fElementChildrenOffsetStack[fElementDepth + 1] + 1;
        int childrenLength = fElementChildrenLength - childrenOffset;
        int result = checkContent(elementIndex,
            children, childrenOffset, childrenLength);

        if (result != -1) {
          fDTDGrammar.getElementDecl(elementIndex, fTempElementDecl);
          if (fTempElementDecl.type == XMLElementDecl.TYPE_EMPTY) {
            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                "MSG_CONTENT_INVALID",
                new Object[]{element.rawname, "EMPTY"},
                XMLErrorReporter.SEVERITY_ERROR);
          } else {
            String messageKey = result != childrenLength ?
                "MSG_CONTENT_INVALID" : "MSG_CONTENT_INCOMPLETE";
            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                messageKey,
                new Object[]{element.rawname,
                    fDTDGrammar.getContentSpecAsString(elementIndex)},
                XMLErrorReporter.SEVERITY_ERROR);
          }
        }
      }
      fElementChildrenLength = fElementChildrenOffsetStack[fElementDepth + 1] + 1;
    }

    endNamespaceScope(fCurrentElement, augs, isEmpty);

    // now pop this element off the top of the element stack
    if (fElementDepth < -1) {
      throw new RuntimeException("FWK008 Element stack underflow");
    }
    if (fElementDepth < 0) {
      fCurrentElement.clear();
      fCurrentElementIndex = -1;
      fCurrentContentSpecType = -1;
      fInElementContent = false;

      // TO DO : fix this
      //
      // Check after document is fully parsed
      // (1) check that there was an element with a matching id for every
      //   IDREF and IDREFS attr (V_IDREF0)
      //
      if (fPerformValidation) {
        String value = fValidationState.checkIDRefID();
        if (value != null) {
          fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
              "MSG_ELEMENT_WITH_ID_REQUIRED",
              new Object[]{value},
              XMLErrorReporter.SEVERITY_ERROR);
        }
      }
      return;
    }

    // If Namespace enable then localName != rawName
    fCurrentElement.setValues(fElementQNamePartsStack[fElementDepth]);

    fCurrentElementIndex = fElementIndexStack[fElementDepth];
    fCurrentContentSpecType = fContentSpecTypeStack[fElementDepth];
    fInElementContent = (fCurrentContentSpecType == XMLElementDecl.TYPE_CHILDREN);

  } // handleEndElement(QName,boolean)

  protected void endNamespaceScope(QName element, Augmentations augs, boolean isEmpty) {

    // call handlers
    if (fDocumentHandler != null && !isEmpty) {
      // NOTE: The binding of the element doesn't actually happen
      //       yet because the namespace binder does that. However,
      //       if it does it before this point, then the endPrefix-
      //       Mapping calls get made too soon! As long as the
      //       rawnames match, we know it'll have a good binding,
      //       so we can just use the current element. -Ac
      fDocumentHandler.endElement(fCurrentElement, augs);
    }
  }

  // returns whether a character is space according to the
  // version of XML this validator supports.
  protected boolean isSpace(int c) {
    return XMLChar.isSpace(c);
  } // isSpace(int):  boolean

  public boolean characterData(String data, Augmentations augs) {
    characters(new XMLString(data.toCharArray(), 0, data.length()), augs);
    return true;
  }

} // class XMLDTDValidator
